﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Xml.Linq;

using ApocDev.TownsApi.Aspects;

namespace ApocDev.TownsApi.XmlFiles
{
    [NotifyPropertyChanged]
    public class ActionsXml : ObservableCollection<Action>, IChangedXml
    {
        public ActionsXml(string actionsPath)
        {
            XElement root = XElement.Load(actionsPath);
            IEnumerable<XElement> actions = root.Elements();
            int total = actions.Count();
            ModFiles.FireLoadEvent(this, new FileLoadEventArgs("Actions", total, 0));
            int numLoaded = 0;
            foreach (XElement xml in actions)
            {
                Add(new Action(xml));
                ModFiles.FireLoadEvent(this, new FileLoadEventArgs("Actions", total, ++numLoaded));
            }
        }

        public bool IsDirty { get { return this.Any(a => a.IsDirty); } }

        public XElement SaveDirty()
        {
            if (!IsDirty)
                return null;
            var actions = new XElement("actions");
            foreach (Action a in this)
            {
                if (a.IsDirty)
                    actions.Add(a.Save());
            }
            return actions;
        }

        public XElement Save()
        {
            var actions = new XElement("actions");
            foreach (Action a in this)
            {
                actions.Add(a.Save());
            }
            return actions;
        }
    }

    [NotifyPropertyChanged]
    public class Action : IChangedXml, IEquatable<Action>
    {
        private readonly int _cleanHash;

        public Action()
        {
            // _cleanHash will be 0.
        }

        public Action(XElement xml)
        {
            foreach (XElement element in xml.Elements())
            {
                switch (element.Name.LocalName.ToLower())
                {
                    case "id":
                        ID = element.Value;
                        break;
                    case "name":
                        Name = element.Value;
                        break;
                    case "priorityid":
                        Priority = ModFiles.GetPriority(element.Value);
                        break;
                    case "queue":
                        Queue = new ObservableCollection<ActionQueueItem>();
                        foreach (XElement e in element.Elements())
                        {
                            Queue.Add(new ActionQueueItem(e));
                        }
                        break;
                    case "generateditem":
                        GeneratedItem = ModFiles.GetItem(element.Value);
                        break;
                    default:
                        MessageBox.Show("Unknown element for " + GetType().Name + ": " + element.Name.LocalName);
                        break;
                }
            }
            _cleanHash = GetHashCode();
        }

        public string ID { get; set; }
        public string Name { get; set; }
        public Priority Priority { get; set; }
        public Item GeneratedItem { get; set; }
        public ObservableCollection<ActionQueueItem> Queue { get; set; }



        // Simplest way to handle this. If the original hash is different, then we've changed *something* in it.
        public bool IsDirty { get { return _cleanHash != GetHashCode(); } }

        public XElement Save()
        {
            var ret = new XElement("action",
                new XElement("id", ID),
                Xml.AddIf("name", Name),
                Xml.AddIf("priority", () => Priority.ID, Priority != null),
                Xml.AddIf("generatedItem", ()=> GeneratedItem.ID, GeneratedItem!=null),
                Xml.AddCollection("queue", Queue));

            return ret;
        }

        public XElement SaveDirty()
        {
            // Entire element part should be saved if we're dirty.
            return Save();
        }

        #region Equality & HashCode

        public bool Equals(Action other)
        {
            if (ReferenceEquals(null, other))
                return false;
            if (ReferenceEquals(this, other))
                return true;
            return string.Equals(ID, other.ID) && string.Equals(Name, other.Name) && Equals(Priority, other.Priority) &&
                   Equals(Queue, other.Queue);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
                return false;
            if (ReferenceEquals(this, obj))
                return true;
            if (obj.GetType() != GetType())
                return false;
            return Equals((Action) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int hashCode = (ID != null ? ID.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Priority != null ? Priority.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Queue != null ? Queue.GetHashCode() : 0);
                return hashCode;
            }
        }

        public static bool operator ==(Action left, Action right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(Action left, Action right)
        {
            return !Equals(left, right);
        }

        #endregion

        public override string ToString()
        {
            // Just "id"
            // Or "id (name)"
            return ID + (Name != null ? " (" + Name + ")" : null);
        }
    }

    public enum ActionQueueType
    {
        Unknown,
        Wait,
        ChangeTerrain,
        CreateFriendly,
        CreateItem,
        DestroyCellItem,
        DestroyItem,
        DestroyFriendly,
        Move,
        MoveTerrain,
        Pick,
        PickFriendly,
        ReplaceCellItem,
        Unlock,
    }

    [NotifyPropertyChanged]
    public class ActionQueueItem : IEquatable<ActionQueueItem>, IChangedXml
    {
        private readonly int _cleanHash;

        public ActionQueueItem()
        {
        }

        public ActionQueueItem(XElement xml)
        {
            ID = xml.Name.LocalName;
            Value = xml.Value;
            LivingEntities = new ObservableCollection<LivingEntity>();
            Items = new ObservableCollection<Item>();

            if (xml.HasAttributes && xml.Attribute("useSource") != null)
                UseSource = xml.Attribute("useSource").Value.ToLower() == "true";

            if (xml.Attribute("fx") != null)
                SoundEffect = xml.Attribute("fx").Value;
            if (xml.Attribute("fxTurns") != null)
                SoundEffectTurns = int.Parse(xml.Attribute("fxTurns").Value);

            _cleanHash = GetHashCode();

            Items = new ObservableCollection<Item>();
            if (!string.IsNullOrWhiteSpace(Value))
            {
                // Yup.
                IEnumerable<string> valueList =
                    Value.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim());
                switch (Type)
                {
                    case ActionQueueType.ChangeTerrain:
                        Terrain = ModFiles.GetTerrain(Value);
                        break;
                    case ActionQueueType.CreateFriendly:
                        LivingEntities.Add(ModFiles.GetLivingEntity(Value));
                        break;
                    case ActionQueueType.CreateItem:
                    case ActionQueueType.ReplaceCellItem:
                        // Limit to 1!!!
                        Items.Add(ModFiles.GetItem(Value));
                        break;
                    case ActionQueueType.Move:
                    case ActionQueueType.Pick:
                        // More than 1 item allowed here.
                        foreach (string val in valueList)
                        {
                            Items.Add(ModFiles.GetItem(val));
                        }

                        break;
                    case ActionQueueType.PickFriendly:
                        // More than 1 item allowed here.
                        foreach (string val in valueList)
                        {
                            LivingEntities.Add(ModFiles.GetLivingEntity(val));
                        }

                        break;
                }
            }
        }

        public string Value { get; set; }

        public bool UseSource { get; set; }
        [Browsable(true), ReadOnly(true)]
        public string ID { get; set; }

        public string SoundEffect { get; set; }
        public int SoundEffectTurns { get; set; }

        public ObservableCollection<Item> Items { get; set; }
        public ObservableCollection<LivingEntity> LivingEntities { get; set; }

        public Terrain Terrain { get; set; }

        [Browsable(true), ReadOnly(true)]
        public ActionQueueType Type
        {
            get
            {
                ActionQueueType t;
                Enum.TryParse(ID, true, out t);
                return t;
            }
        }

        public bool IsDirty { get { return _cleanHash != GetHashCode(); } }

        public XElement Save()
        {
            var ret = new XElement(ID.CamelToXmlTag(), BuildValue());
            if (UseSource)
                ret.Add(new XAttribute("useSource", true));
            if (!string.IsNullOrWhiteSpace(SoundEffect))
                ret.Add(new XAttribute("fx", SoundEffect));
            if(SoundEffectTurns != 0)
                ret.Add(new XAttribute("fxTurns", SoundEffectTurns));
            return ret;
        }

        public XElement SaveDirty()
        {
            return Save();
        }

        public bool Equals(ActionQueueItem other)
        {
            if (ReferenceEquals(null, other))
                return false;
            if (ReferenceEquals(this, other))
                return true;
            return string.Equals(Value, other.Value) && UseSource.Equals(other.UseSource) && string.Equals(ID, other.ID);
        }

        public override string ToString()
        {
            return Type.ToString().CamelToSpaced() + " (" + Value + ")";
        }

        private string BuildValue()
        {
            switch (Type)
            {
                case ActionQueueType.ChangeTerrain:
                    return Terrain.ID;

                case ActionQueueType.CreateItem:
                case ActionQueueType.ReplaceCellItem:
                case ActionQueueType.Move:
                case ActionQueueType.Pick:
                    return string.Join(",", Items);

                case ActionQueueType.CreateFriendly:
                case ActionQueueType.PickFriendly:
                    return string.Join(",", LivingEntities);

                    // These don't need to be defined, they're here for completeness so we know right away which
                    // types should have no value.
                case ActionQueueType.MoveTerrain:
                case ActionQueueType.Unlock:
                case ActionQueueType.DestroyCellItem:
                case ActionQueueType.DestroyItem:
                case ActionQueueType.DestroyFriendly:
                default:
                    return null;
            }
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
                return false;
            if (ReferenceEquals(this, obj))
                return true;
            if (obj.GetType() != GetType())
                return false;
            return Equals((ActionQueueItem) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int hashCode = (Value != null ? Value.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ UseSource.GetHashCode();
                hashCode = (hashCode * 397) ^ (ID != null ? ID.GetHashCode() : 0);
                return hashCode;
            }
        }

        public static bool operator ==(ActionQueueItem left, ActionQueueItem right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(ActionQueueItem left, ActionQueueItem right)
        {
            return !Equals(left, right);
        }
    }
}